# -*- coding: utf-8 -*-

# File Name： load_config
# Description :
# Author : 'li'
# date： 2022/5/23
# Change Activity:
# 2022/5/23:
import shutil
import warnings

from ..config.py_loader import PyLoader
from ..dict_wrapper import Dict
from ..path import join_relative_path, get_file_name_suffix, check_file_exist, create_dir, join_path, get_absolute_dir

BASE_KEY = '_base_'
DELETE_KEY = '_delete_'
DEPRECATION_KEY = '_deprecation_'
RESERVED_KEYS = ['_file_path', '_text', '_pretty_text']


class ConfigLoader:
    """
    加载配置文件
    """

    @staticmethod
    def load_from_file(relative_file_path):
        """
        加载配置文件

        Args:
            relative_file_path: relative path.

        Returns:

        """
        abs_file_path = join_relative_path(relative_file_path)
        ext_name = get_file_name_suffix(abs_file_path)
        if ext_name not in ['.py', '.json', '.yaml', '.yml']:
            raise IOError('Only py/yml/yaml/json type are supported now!')
        loader = None
        if '.py' == ext_name:
            loader = PyLoader(relative_file_path)
        cfg = loader.load_cfg()
        ConfigLoader._check_deprecation_info(cfg, relative_file_path)
        base_cfg_lst = []
        if BASE_KEY in cfg and len(cfg[BASE_KEY]) > 0:
            for file_path in cfg[BASE_KEY]:
                base_config = ConfigLoader.load_from_file(file_path)
                base_cfg_lst.append(base_config)
        base_cfg_dict = dict()
        for c in base_cfg_lst:
            duplicate_keys = base_cfg_dict.keys() & c.keys()
            if len(duplicate_keys) > 0:
                raise KeyError('Duplicate key is not allowed among bases. Duplicate keys: {duplicate_keys}')
            base_cfg_dict.update(c)
        cfg = ConfigLoader.merge_a_into_b(cfg, base_cfg_dict)
        return Dict(cfg)

    @staticmethod
    def merge_a_into_b(a, b, allow_list_keys=False):
        """合并两个dict a和b,如果a和b的字段有冲突，a将覆盖b.


        Args:
            a (dict): The source dict to be merged into ``b``.
            b (dict): The origin dict to be fetch keys from ``a``.
            allow_list_keys (bool): If True, int string keys (e.g. '0', '1')
              are allowed in source ``a`` and will replace the element of the
              corresponding index in b if b is a list. Default: False.

        Returns:
            dict: The modified dict of ``b`` using ``a``.

        Examples:
            # Normally merge a into b.
            # >>> Config.merge_a_into_b(
            # ...     dict(obj=dict(a=2)), dict(obj=dict(a=1)))
            {'obj': {'a': 2}}

            # Delete b first and merge a into b.
            # >>> Config.merge_a_into_b(
            # ...     dict(obj=dict(_delete_=True, a=2)), dict(obj=dict(a=1)))
            {'obj': {'a': 2}}

            # b is a list
            # >>> Config.merge_a_into_b(
            # ...     {'0': dict(a=2)}, [dict(a=1), dict(b=2)], True)
            # [{'a': 2}, {'b': 2}]
        """
        b = b.copy()
        for k, v in a.items():
            if allow_list_keys and k.isdigit() and isinstance(b, list):
                k = int(k)
                if len(b) <= k:
                    raise KeyError(f'Index {k} exceeds the length of list {b}')
                b[k] = ConfigLoader.merge_a_into_b(v, b[k], allow_list_keys)
            elif isinstance(v, dict):
                if k in b and not v.pop(DELETE_KEY, False):
                    allowed_types = (dict, list) if allow_list_keys else dict
                    if not isinstance(b[k], allowed_types):
                        raise TypeError(
                            f'{k}={v} in child config cannot inherit from '
                            f'base because {k} is a dict in the child config '
                            f'but is of type {type(b[k])} in base config. '
                            f'You may set `{DELETE_KEY}=True` to ignore the '
                            f'base config.')
                    b[k] = ConfigLoader.merge_a_into_b(v, b[k], allow_list_keys)
                else:
                    b[k] = Dict(v)
            else:
                b[k] = v
        return b

    @staticmethod
    def _check_deprecation_info(cfg_dict, filename):
        """
        检查deprecation

        Args:
            cfg_dict:
            filename:

        Returns:

        """
        if DEPRECATION_KEY in cfg_dict:
            deprecation_info = cfg_dict.pop(DEPRECATION_KEY)
            warning_msg = f'The config file {filename} will be deprecated ' \
                          'in the future.'
            if 'expected' in deprecation_info:
                warning_msg += f' Please use {deprecation_info["expected"]} ' \
                               'instead.'
            if 'reference' in deprecation_info:
                warning_msg += ' More information can be found at ' \
                               f'{deprecation_info["reference"]}'
            warnings.warn(warning_msg)


def _load_default_config():
    """
    读取工程目录下的config/default_config.py文件，如果该文件不存在，复制config_template.py到config/default_config.py

    """

    default_path = join_relative_path('config/default_config.py')
    if not check_file_exist(default_path):
        create_dir(join_relative_path('config'))
        current_path = get_absolute_dir(__file__)
        shutil.copy(join_path(current_path, 'config_template.py'), default_path)
        warnings.warn('default profile does not exist, use template file.')


_load_default_config()


def test_config_loader():
    config = ConfigLoader.load_from_file(join_relative_path('config/default_config.py'))
    print(config)
